﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
// Working, but somewhat experimental
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows;
using BrainTechLLC.ThreadSafeObjects;
using BrainTechLLC;
using System.ComponentModel;
using System.Diagnostics;

namespace BrainTechLLC
{
    public class UserConnection
    {
        public UserConnection()
        {
            Lock = new Lockable();
            IncomingBytes = new MemoryStream();
            OutgoingBytes = new MemoryStream();
        }

        public Lockable Lock;
        public string Host { get; set; }
        public int Port { get; set; }
        public Socket Socket { get; set; }
        public MemoryStream IncomingBytes { get; set; }
        public MemoryStream OutgoingBytes { get; set; }
    }

    public class SilverlightCommClient : INotifyPropertyChanged
    {
        public static readonly int PortNumber = 4502;
        public event EventHandler<ObjectEventArgs<UserConnection>> ConnectionEstablished;
        public event EventHandler<ObjectEventArgs<SocketAsyncEventArgs>> ConnectionError;
        public event EventHandler<ObjectEventArgs<SocketAsyncEventArgs>> SocketErrorOccurred;
        public event EventHandler<ObjectEventArgs<byte[]>> ReceivedData;

        public Socket _socket;
        public byte[] _socketBytes = new byte[1000000];
        public long _bytesSent;
        public long _bytesRecv;

        public void OnPropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }

        public long BytesSent
        {
            get
            {
                return _bytesSent;
            }
            set
            {
                _bytesSent = value;
                OnPropertyChanged("BytesSent");
            }
        }

        public long BytesReceived
        {
            get { return _bytesRecv; }
            set
            {
                _bytesRecv = value;
                OnPropertyChanged("BytesReceived");
            }
        }

        public bool Connected
        {
            get
            {
                if (_socket == null)
                    return false;

                return _socket.Connected;
            }
        }

        public bool ConnectToServer(string host)
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            UserConnection conn = new UserConnection() { Socket = _socket, Port = PortNumber };
            DnsEndPoint ep = null;

            if (string.IsNullOrEmpty(host))
            {
                try
                {
                    ep = new DnsEndPoint(Application.Current.Host.Source.DnsSafeHost, PortNumber);
                    conn.Host = Application.Current.Host.Source.DnsSafeHost;
                }
                catch { }

                try
                {
                    ep = new DnsEndPoint("127.0.0.1", PortNumber);
                    conn.Host = "127.0.0.1";
                }
                catch { }
            }
            else
            {
                ep = new DnsEndPoint(host, PortNumber);
                conn.Host = host;
            }

            SocketAsyncEventArgs args = new SocketAsyncEventArgs() { RemoteEndPoint = ep };

            args.UserToken = conn;
            args.Completed += new EventHandler<SocketAsyncEventArgs>(ConnectionComplete);

            _socket.ConnectAsync(args);
            return true;
        }

        protected void ConnectionComplete(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                UserConnection s = (UserConnection)e.UserToken;
                e.SetBuffer(_socketBytes, 0, 1000000);
                s.Socket.ReceiveAsync(e);
                e.Completed -= ConnectionComplete;
                e.Completed += SocketCommunicationComplete;

                if (ConnectionEstablished != null)
                    ConnectionEstablished(this, new ObjectEventArgs<UserConnection>(s));

                return;
            }
            else if (e.SocketError == SocketError.AccessDenied)
            {
                Debug.WriteLine(e.SocketError.ToString());
                Console.WriteLine(e.SocketError.ToString());
            }
            else
            {
                Debug.WriteLine(e.SocketError.ToString());
                Console.WriteLine(e.SocketError.ToString());
            }

            if (ConnectionError != null)
                ConnectionError(this, new ObjectEventArgs<SocketAsyncEventArgs>(e));
        }

        public void SendMessage(ICanCustomSerialize obj)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    bw.Seek(8, SeekOrigin.Begin);
                    int byteCount = obj.SerializeOut(bw);
                    bw.Seek(0, SeekOrigin.Begin);
                    bw.Write(byteCount);
                    bw.Write(obj.TransmissionType);

                    SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                    byte[] bb = new byte[(int)ms.Length];
                    ms.ToArray().CopyTo(bb, 0);
                    e.SetBuffer(bb, 0, bb.Length);
                    Interlocked.Add(ref _bytesSent, bb.Length);
                    e.Completed += new EventHandler<SocketAsyncEventArgs>(e_Completed);
                    _socket.SendAsync(e);
                    OnPropertyChanged("BytesSent");
                }
            }
        }

        public void SendBytes(int type, byte[] bytes)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    // TODO - herehere
                    bw.Write(bytes.Length + 1);
                    bw.Write(type);
                    bw.Write(bytes, 0, bytes.Length);
                    SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                    byte[] bb = new byte[(int)ms.Length];
                    ms.ToArray().CopyTo(bb, 0);
                    e.SetBuffer(bb, 0, bb.Length);
                    Interlocked.Add(ref _bytesSent, bb.Length);
                    e.Completed += new EventHandler<SocketAsyncEventArgs>(e_Completed);
                    _socket.SendAsync(e);
                    OnPropertyChanged("BytesSent");
                }
            }
        }

        void e_Completed(object sender, SocketAsyncEventArgs e)
        {
            Console.WriteLine("Completed");
        }

        void HandleIncomingData(MemoryStream ms, UserConnection s)
        {
            byte[] bytes = null;

            ms.Seek(0, SeekOrigin.Begin);
            byte[] lenBytes = new byte[4];
            ms.Read(lenBytes, 0, 4);
            int len;
            bool tryAgain = false;
            bool read = true;

            using (MemoryStream msRead = new MemoryStream(lenBytes))
            {
                using (BinaryReader br = new BinaryReader(msRead))
                {
                    len = br.ReadInt32();
                    if (ms.Length < len + 8)
                    {
                        ms.Seek(0, SeekOrigin.End);
                        read = false;
                    }
                }
            }

            if (read)
            {
                bytes = new byte[len + 4];
                int totalLength = (int)ms.Length;
                ms.Seek(4, SeekOrigin.Begin);
                ms.Read(bytes, 0, len + 4);

                int bytesLeftCount = totalLength - len - 8;
                if (bytesLeftCount > 0)
                {
                    byte[] bytesLeft = new byte[bytesLeftCount];
                    ms.Read(bytesLeft, 0, bytesLeftCount);

                    s.IncomingBytes = new MemoryStream();
                    s.IncomingBytes.Write(bytesLeft, 0, bytesLeftCount);
                    tryAgain = true;
                }
                else
                {
                    s.IncomingBytes = new MemoryStream();
                }
            }

            if (ReceivedData != null && read)
                ReceivedData(this, new ObjectEventArgs<byte[]>(ms.ToArray()));

            if (tryAgain)
            {
                HandleIncomingData(s.IncomingBytes, s);
            }
        }


        protected void SocketCommunicationComplete(object sender, SocketAsyncEventArgs e)
        {
            UserConnection s = (UserConnection)e.UserToken;

            if (e.SocketError == SocketError.Success)
            {
                try
                {
                    switch (e.LastOperation)
                    {
                        case SocketAsyncOperation.Receive:
                            s.Lock.AquireLock();
                            {
                                byte[] buffer = new byte[e.BytesTransferred];
                                Array.Copy(e.Buffer, e.Offset, buffer, 0, e.BytesTransferred);
                                Interlocked.Add(ref _bytesRecv, buffer.Length);
                                OnPropertyChanged("BytesReceived");

                                MemoryStream ms = s.IncomingBytes;
                                ms.Write(buffer, 0, e.BytesTransferred);
                                Interlocked.Add(ref _bytesRecv, e.BytesTransferred);

                                HandleIncomingData(ms, s);
                                
                                s.Socket.ReceiveAsync(e);
                            }
                            s.Lock.ReleaseLock();

                            break;
                        case SocketAsyncOperation.Send:
                            Console.Write("ugh");
                            break;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString() + Environment.NewLine + ex.StackTrace);
                    Debug.WriteLine(ex.ToString() + Environment.NewLine + ex.StackTrace);
                }
            }
            else
            {
                if (SocketErrorOccurred != null)
                    SocketErrorOccurred(this, new ObjectEventArgs<SocketAsyncEventArgs>(e));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}
